#include <z80.h>
#include <stddef.h>
#include "sio.h"

// port address
enum sio_port {
    SIO_REG = 0xDE,
    SIO_DATA = 0xDF
};

// control register
static uint8_t _cr = 0x00;

static uint8_t sio_status(void)
{
    return z80_inp(SIO_REG);
}

static uint16_t sio_write(void *data, uint16_t len)
{
    uint16_t counter = 0;
    uint8_t tc = 0;
    for (uint16_t i = 0; i < len; i++) {
        // wait until transmit data register empty
        while ((sio_status() & SIO_TDRE) == 0) {
            if (tc++ > 50) return counter;
        }
        tc = 0;
        z80_outp(SIO_DATA, ((uint8_t*)data)[i]);
        counter++;
    }
    return counter;
}

static uint16_t sio_read(void *data, uint16_t len)
{
    uint16_t counter = 0;
    uint8_t tc = 0;
    while (counter < len) {
        // wait until receive data register full
        while ((sio_status() & SIO_RDRF) == 0) {
            if (tc++ > 50) return counter;
        }
        tc = 0;
        ((uint8_t*)data)[counter] = z80_inp(SIO_DATA);
        counter++;
    }
    return counter;
}

void sio_init(struct sio_device *dev, struct sio_context *ctx)
{
    if (ctx == NULL)
        return;

    dev->status = sio_status;
    dev->write = sio_write;
    dev->read = sio_read;

    // databit (CR4)
    if (ctx->databit == SIO_DATABIT_8)
        _cr |= 0x10;

    // stopbit (CR3)
    if (ctx->stopbit == SIO_STOPBIT_1)
        _cr |= 0x08;

    // parity (CR2)
    if (ctx->parity == SIO_PARITY_ODD)
        _cr |= 0x04;

    if (ctx->parity == SIO_PARITY_NONE && ctx->databit == SIO_DATABIT_8) {
        _cr &= 0xF3;
        if (ctx->stopbit == SIO_STOPBIT_1)
            _cr |= 0x04;
    }

    // transmit interrupt (CR5 CR6)
    if (ctx->interrupt & SIO_INTERRUPT_TX)
        _cr |= 0x20;

    // receive interrupt (CR7)
    if (ctx->interrupt & SIO_INTERRUPT_RX)
        _cr |= 0x80;

    // reset ACIA (CR1 CR0)
    _cr &= 0xFC;
    _cr |= 0x03;
    z80_outp(SIO_REG, _cr);

    // set counter devide ratios = 64, 7.3728MHz / 64 = 115200bps (CR1 CR0)
    _cr &= 0xFC;
    _cr |= 0x02;
    z80_outp(SIO_REG, _cr);
}